﻿Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Security.Cryptography
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Shapes

Partial Public Class Page
    Inherits UserControl
    Private keyHandler As KeyHandler
    Private gameLoop As GameLoop
    Private _PlayerShip As Ship
    Private Property PlayerShip() As Ship
        Get
            Return _PlayerShip
        End Get
        Set(ByVal value As Ship)
            _PlayerShip = value
        End Set
    End Property
    Private aliens As List(Of Alien)
    Private aliensRemove As List(Of Alien)
    Private alienShooters As List(Of Alien)
    Private bombs As List(Of Bomb)
    Private bombsRemove As List(Of Bomb)
    Private missles As List(Of Missle)
    Private misslesRemove As List(Of Missle)
    Private waves As List(Of WaveData)

    Private spritesCount As Integer
    Private alienCount As Integer = 5
    Private shotsAtOnce As Integer = 1
    Private shotsThisPass As Integer = 1
    Private corridorHeight As Double
    Private corridorPad As Double = 15
    Private alienSpeed As Double = 100
    Private alternate As Boolean = True
    Private enemyShootMilliseonds As Double = 3000
    Private spawnFadeInMilliseconds As Double = 3000

    Public status As GameState

    Private shoot As Boolean = False

    Private nextShot As DateTime = DateTime.Now

    Public Sub New()
        InitializeComponent()
        keyHandler = New KeyHandler(Me)
        GenerateStarField(350)
        InitializeGame()
    End Sub

    Private Sub InitializeGame()
        gameLoop = New GameLoop(Me)
        AddHandler gameLoop.Update, AddressOf gameLoop_Update

        PlayerShip = New Ship(30, 30, New Point(100, 360))
        PlayerShip.MaxX = 470
        PlayerShip.MinX = 10
        gameRoot.Children.Add(PlayerShip.SpriteCanvas)

        corridorHeight = ((gameRoot.Height - 100) / alienCount) - 15

        waves = New List(Of WaveData)()
        waves.Add(New WaveData(10, 3000, 2, 1))
        waves.Add(New WaveData(11, 2000, 4, 5))
        waves.Add(New WaveData(12, 1000, 6, 1))
        waves.Add(New WaveData(15, 1000, 10, 1))
        waves.Add(New WaveData(500, 1000, 11, 5))
        waves.Add(New WaveData(20, 500, 12, 1))
        waves.Add(New WaveData(20, 500, 13, 2))
        waves.Add(New WaveData(30, 500, 20, 3))
        waves.Add(New WaveData(50, 300, 25, 3))
        waves.Add(New WaveData(100, 300, 30, 5))
        waves.Add(New WaveData(200, 300, 40, 5))
        waves.Add(New WaveData(500, 300, 50, 5))

        aliens = New List(Of Alien)()
        aliensRemove = New List(Of Alien)()
        alienShooters = New List(Of Alien)()
        bombs = New List(Of Bomb)()
        bombsRemove = New List(Of Bomb)()
        missles = New List(Of Missle)()
        misslesRemove = New List(Of Missle)()

        StartWave()

        ctlLives.Lives = 3

        gameLoop.Start()
        status = GameState.Running
    End Sub

    Private Sub gameLoop_Update(ByVal elapsed As TimeSpan)
        'clear the current Vector so the sprite is not moving unless a keys is pressed 
        PlayerShip.Velocity = New Vector(0, 0)
        If keyHandler.IsKeyPressed(Key.Left) Then
            PlayerShip.Velocity = Vector.CreateVectorFromAngle(270, 125)
        End If
        If keyHandler.IsKeyPressed(Key.Right) Then
            PlayerShip.Velocity = Vector.CreateVectorFromAngle(90, 125)
        End If
        If keyHandler.IsKeyPressed(Key.Space) Then
            Select Case status
                Case GameState.Ready
                    Exit Select
                Case GameState.Running
                    EntityFired(PlayerShip)
                    Exit Select
                Case GameState.Paused
                    Exit Select
                Case GameState.BetweenWaves
                    status = GameState.Running
                    ctlInfo.GameInfo = ""
                    StartWave()
                    Exit Select
                Case GameState.GameOver
                    Exit Select
                Case Else
                    Exit Select
            End Select
        End If
        PlayerShip.Update(elapsed)

        BombLoop(elapsed)
        MissleLoop(elapsed)
        AlienLoop(elapsed)

        For Each alien As Alien In aliensRemove
            aliens.Remove(alien)
            gameRoot.Children.Remove(alien.SpriteCanvas)
            AlienShot(alien)
        Next
        aliensRemove.Clear()

        For Each missle As Missle In misslesRemove
            missles.Remove(missle)
            gameRoot.Children.Remove(missle.SpriteCanvas)
        Next
        misslesRemove.Clear()

        If nextShot <= DateTime.Now Then
            nextShot = DateTime.Now.AddMilliseconds(enemyShootMilliseonds).AddMilliseconds(elapsed.Milliseconds * -1)

            shotsThisPass = shotsAtOnce
            If shotsThisPass > aliens.Count Then
                shotsThisPass = aliens.Count
            End If

            If aliens.Count > 0 Then
                For Each alien As Alien In aliens
                    alienShooters.Add(alien)
                Next
            End If

            While alienShooters.Count > shotsThisPass
                alienShooters.RemoveAt(GetRandInt(0, alienShooters.Count - 1))
            End While

            For Each alien As Alien In alienShooters
                EntityFired(alien)
            Next

            alienShooters.Clear()
        End If
    End Sub

    Private Sub EntityFired(ByVal shooter As Sprite)
        Debug.WriteLine(shooter)
        Select Case shooter.ToString()
            Case "SimpleShooter.Ship"
                If missles.Count = 0 Then
                    Dim missle As Missle = DirectCast(shooter, Ship).Fire()
                    missles.Add(missle)

                    gameRoot.Children.Add(missle.SpriteCanvas)
                End If
                Exit Select
            Case "SimpleShooter.Alien"
                Dim bomb As Bomb = DirectCast(shooter, Alien).Fire()
                bombs.Add(bomb)
                gameRoot.Children.Add(bomb.SpriteCanvas)
                Exit Select
            Case Else
                Exit Select
        End Select
    End Sub

    Public Sub AlienShot(ByVal alien As Alien)
        If waves.Count > 0 Then
            If waves(0).EnemyCount > 0 Then
                NextFromWave(alien.Position.Y - (corridorPad + alien.SpriteCanvas.Height))
            Else
                If aliens.Count = 0 Then
                    'wave over 
                    waves.RemoveAt(0)
                    If waves.Count > 0 Then
                        ctlInfo.GameInfo = String.Format("{0}{1}{2}", "Wave Defeated", Environment.NewLine, "(fire to continue)")
                        status = GameState.BetweenWaves
                    Else
                        GameOver()
                    End If
                End If
            End If
        Else

            If aliens.Count < 1 Then
                GameOver()
            End If

        End If
    End Sub

    Private Sub BombLoop(ByVal elapsed As TimeSpan)
        For Each bomb As Bomb In bombs
            bomb.Update(elapsed)
            If bomb.Position.Y > gameRoot.Height Then
                gameRoot.Children.Remove(bomb.SpriteCanvas)
                bombsRemove.Add(bomb)
            Else
                If Sprite.Collides(PlayerShip, bomb) Then
                    ctlLives.Lives -= 1

                    If ctlLives.Lives < 1 Then
                        GameOver()
                    Else
                        bombsRemove.Add(bomb)
                        PlayerShip.FadeStoryboard.Begin()

                    End If
                End If
            End If
        Next
        For Each bomb As Bomb In bombsRemove
            bombs.Remove(bomb)
            gameRoot.Children.Remove(bomb.SpriteCanvas)
        Next
        bombsRemove.Clear()
    End Sub

    Private Sub MissleLoop(ByVal elapsed As TimeSpan)
        For Each missle As Missle In missles
            missle.Update(elapsed)
            If missle.Position.Y < 0 Then
                gameRoot.Children.Remove(missle.SpriteCanvas)
                misslesRemove.Add(missle)

            End If
        Next

        For Each missle As Missle In misslesRemove
            missles.Remove(missle)
            gameRoot.Children.Remove(missle.SpriteCanvas)
        Next

        misslesRemove.Clear()
    End Sub

    Private Sub AlienLoop(ByVal elapsed As TimeSpan)
        For Each alien As Alien In aliens
            alien.Update(elapsed)
            alien.CheckDirection()

            If alien.spawnWait Then
                If alien.spawnComplete <= DateTime.Now Then
                    alien.spawnWait = False
                End If
            End If

            If Not alien.spawnWait Then
                For Each missle As Missle In missles
                    If Sprite.Collides(alien, missle) Then
                        aliensRemove.Add(alien)
                        misslesRemove.Add(missle)
                        ctlScore.GameScore += 10
                    End If
                Next
            End If
        Next
    End Sub

    Private Sub GenerateStarField(ByVal numberOfStars As Integer)

        For i As Integer = 0 To numberOfStars - 1

            Dim star As New Ellipse()
            Dim size As Double = GetRandInt(10, 800) * 0.01
            star.Width = size
            star.Height = size
            star.Opacity = GetRandInt(1, 5) * 0.1
            star.Fill = New SolidColorBrush(Colors.White)
            Dim x As Integer = GetRandInt(0, CInt(Math.Round(gameRoot.Height, 0)))
            Dim y As Integer = GetRandInt(0, CInt(Math.Round(gameRoot.Width, 0)))
            star.SetValue(Canvas.TopProperty, CDbl(x))
            star.SetValue(Canvas.LeftProperty, CDbl(y))
            gameRoot.Children.Add(star)
        Next
    End Sub

    Public Function GetRandInt(ByVal min As Integer, ByVal max As Integer) As Integer
        Dim rndBytes As [Byte]() = New [Byte](9) {}
        Dim rndC As New RNGCryptoServiceProvider()
        rndC.GetBytes(rndBytes)
        Dim seed As Integer = BitConverter.ToInt32(rndBytes, 0)
        Dim rand As New Random(seed)
        Return rand.[Next](min, max)
    End Function

    Public Sub StartWave()
        If waves.Count > 0 Then

            Dim alien As New Alien(30, 30, New Point(0, 0))
            For i As Integer = 0 To waves(0).enemiesAtOnce - 1
                Dim row As Integer = i
                While row > 5
                    row -= 5
                End While
                AddAlien(corridorHeight * row, 0)
                waves(0).EnemyCount -= 1
                shotsAtOnce = waves(0).fireAtOnce
                enemyShootMilliseonds = waves(0).fireRateMilliseconds
            Next

        End If
    End Sub

    Public Sub NextFromWave(ByVal startY As Double)
        If waves.Count > 0 Then
            Dim alien As New Alien(30, 30, New Point(0, 0))
            AddAlien(startY, spawnFadeInMilliseconds)
            waves(0).EnemyCount -= 1
            If waves(0).EnemyCount = 0 Then
                'then that was the last one for that wave 
                waves(0).waveEmpty = True
            End If
        End If
    End Sub

    Public Sub AddAlien(ByVal startY As Double, ByVal fadeInMilliseconds As Double)
        Dim alien As New Alien(30, 30, New Point(0, 0))
        spritesCount += 1
        alien.MinX = 1
        alien.MaxX = gameRoot.Width - 1
        alien.speed = alienSpeed
        alien.wayPointMax = gameRoot.Width - (alien.SpriteCanvas.Width / 2) - 10
        alien.wayPointMin = (alien.SpriteCanvas.Width / 2) + 10

        alien.Position = New Point(GetRandInt(CInt(Math.Round(alien.wayPointMin, 0)), CInt(Math.Round(alien.wayPointMax, 0))), startY + corridorPad + alien.SpriteCanvas.Height)

        If alternate Then
            alien.Velocity = Vector.CreateVectorFromAngle(90, alienSpeed)
            alternate = False
        Else
            alien.Velocity = Vector.CreateVectorFromAngle(270, alienSpeed)
            alternate = True
        End If

        If fadeInMilliseconds > 0 Then
            alien.spawnWait = True
            alien.spawnComplete = DateTime.Now.AddMilliseconds(fadeInMilliseconds)
            Dim tsDuration As TimeSpan = TimeSpan.FromMilliseconds(fadeInMilliseconds)
            Dim durationTimeline As New Duration(tsDuration)
            Dim spawnFade As New DoubleAnimation()
            Dim storyBoard__1 As New Storyboard()

            spawnFade.From = 0.1
            spawnFade.[To] = 1
            spawnFade.SetValue(Storyboard.TargetNameProperty, DirectCast(alien.SpriteCanvas, Canvas).Name)
            spawnFade.SetValue(Storyboard.TargetPropertyProperty, New PropertyPath("Opacity"))
            spawnFade.Duration = durationTimeline
            storyBoard__1.Children.Add(spawnFade)
            DirectCast(alien.SpriteCanvas, Canvas).Resources.Add("SpawnFadeAnimation", storyBoard__1)
            storyBoard__1.Begin()
        End If

        aliens.Add(alien)

        gameRoot.Children.Add(alien.SpriteCanvas)
    End Sub

    Public Sub GameOver()
        ctlInfo.GameInfo = String.Format("GAME OVER")
        status = GameState.GameOver
        gameLoop.[Stop]()
    End Sub

    Public Enum GameState
        Ready = 0
        Running = 1
        Paused = 2
        BetweenWaves = 3
        GameOver = 4
    End Enum
End Class
